package com.mercury.core.manager;


import com.alibaba.fastjson.JSONObject;
import com.mercury.core.manager.lock.Lock;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


/**
 * @author mercury
 */
@Component
@Slf4j
public class RedisManager {

    private static final Long RELEASE_SUCCESS = 1L;
    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";

    /**
     * 单个业务持有锁的时间30s，防止死锁
     */
    private final static long LOCK_EXPIRE = 30 * 1000L;

    /**
     * 默认30ms尝试一次
     */
    private final static long LOCK_TRY_INTERVAL = 30L;

    /**
     * 默认尝试20s
     */
    private final static long LOCK_TRY_TIMEOUT = 20 * 1000L;


    public static final Long ONE_DAY_SECOND = 86400L;

    /**
     * 当前设置 过期时间单位, EX = seconds; PX = milliseconds
     */
    private static final String SET_WITH_EXPIRE_TIME = "EX";
    /**
     * lua
     */
    private static final String RELEASE_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

    @Resource
    public StringRedisTemplate template;


    /**
     * 批量删除
     *
     * @param keys
     */
    public void remove(final String... keys) {
        template.delete(Arrays.asList(keys));
    }

    /**
     * 批量删除
     *
     * @param keys
     */
    public void remove(final Set<String> keys) {
        template.delete(keys);
    }

    /**
     * 单个删除
     *
     * @param key
     */
    public void remove(final String key) {
        template.delete(key);
    }

    /**
     * 查询过期时间
     *
     * @param key
     * @return
     */
    public Long ttl(final String key) {
        return template.getExpire(key);
    }

    /**
     * 查找匹配的key
     *
     * @param pattern
     * @return
     */
    public Set<String> keys(String pattern) {
        return template.keys(pattern);
    }

    /**
     * 获取过期时间
     *
     * @param key
     * @return
     */
    public Long getExpire(String key) {
        return template.getExpire(key);
    }

    /**
     * 是否存在key
     *
     * @param key
     * @return
     */
    public Boolean hasKey(final String key) {
        return template.hasKey(key);
    }

    /**
     * 设置过期时间
     *
     * @param key
     * @param timeout
     */
    public void expire(final String key, Long timeout) {
        expire(key, timeout, TimeUnit.SECONDS);
    }

    /**
     * 设置过期时间
     *
     * @param key
     * @param timeout
     * @param unit
     */
    public void expire(final String key, Long timeout, TimeUnit unit) {
        template.expire(key, timeout, unit);
    }

    /**
     * 设置过期时间
     *
     * @param key
     * @param timeout
     * @param unit
     * @return
     */
    public Boolean expire(String key, long timeout, TimeUnit unit) {
        return template.expire(key, timeout, unit);
    }

    /**
     * 移除 key 的过期时间，key 将持久保持
     *
     * @param key
     * @return
     */
    public Boolean persist(String key) {
        return template.persist(key);
    }

    /**
     * 将当前数据库的 key 移动到给定的数据库 db 当中
     *
     * @param key
     * @param dbIndex
     * @return
     */
    public Boolean move(String key, int dbIndex) {
        return template.move(key, dbIndex);
    }

    /**
     * 从当前数据库中随机返回一个 key
     *
     * @return
     */
    public String randomKey() {
        return template.randomKey();
    }

    /**
     * 修改 key 的名称
     *
     * @param oldKey
     * @param newKey
     */
    public void rename(String oldKey, String newKey) {
        template.rename(oldKey, newKey);
    }

    /**
     * 仅当 newkey 不存在时，将 oldKey 改名为 newkey
     *
     * @param oldKey
     * @param newKey
     * @return
     */
    public Boolean renameIfAbsent(String oldKey, String newKey) {
        return template.renameIfAbsent(oldKey, newKey);
    }

    /**
     * 返回 key 所储存的值的类型
     *
     * @param key
     * @return
     */
    public DataType type(String key) {
        return template.type(key);
    }

    /*------------------Value Operation------------------*/

    /**
     * 设置指定 key 的值
     *
     * @param key
     * @param value
     */
    public void set(final String key, String value) {
        template.opsForValue().set(key, value);
    }

    /**
     * 设置指定 key 的值
     *
     * @param key
     * @param value
     * @param expireSeconds
     * @param unit
     */
    public void set(final String key, String value, Long expireSeconds, TimeUnit unit) {
        template.opsForValue().set(key, value, expireSeconds, unit);
    }

    /**
     * 设置指定 key 的值
     *
     * @param key
     * @param value
     * @param expireSeconds
     */
    public void set(final String key, String value, Long expireSeconds) {
        template.opsForValue().set(key, value, expireSeconds, TimeUnit.SECONDS);
    }

    /**
     * 只有在 key 不存在时设置 key 的值
     *
     * @param key
     * @param value
     * @return 之前已经存在返回false, 不存在返回true
     */
    public Boolean setIfAbsent(String key, String value) {
        return template.opsForValue().setIfAbsent(key, value);
    }

    /**
     * 获取指定 key 的值
     *
     * @param key
     * @return
     */
    public Optional<String> get(final String key) {
        return Optional.ofNullable(template.opsForValue().get(key));
    }

    /**
     * 获取指定 key 的值
     *
     * @param key
     * @return
     */
    public String getString(final String key) {
        return template.opsForValue().get(key);
    }

    /**
     * 设置指定 key 的值,并且帮助传入的 value 转json
     *
     * @param key
     * @param value
     * @param <V>
     */
    public <V> void set(final String key, final V value) {
        String json = JSONObject.toJSONString(value);
        template.opsForValue().set(key, json);
    }

    /**
     * 设置指定 key 的值,并且帮助传入的 value 转json
     *
     * @param key
     * @param value
     * @param expireSeconds
     * @param <V>
     */
    public <V> void set(final String key, final V value, final Long expireSeconds) {
        String json = JSONObject.toJSONString(value);
        template.opsForValue().set(key, json, expireSeconds, TimeUnit.SECONDS);
    }

    /**
     * 设置指定 key 的值,并且帮助传入的 value 转json
     *
     * @param key
     * @param value
     * @param expireSeconds
     * @param unit
     * @param <V>
     */
    public <V> void set(final String key, final V value, final Long expireSeconds, TimeUnit unit) {
        String json = JSONObject.toJSONString(value);
        template.opsForValue().set(key, json, expireSeconds, unit);
    }

    public void setBit(String key, Integer i, Boolean flag) {
        template.opsForValue().setBit(key, i, flag);
    }

    /**
     * 获取指定 key 的值,并且帮助转换需要的对象
     *
     * @param key
     * @param valueType
     * @param <V>
     * @return
     */
    public <V> Optional<V> get(final String key, final Class<V> valueType) {
        return Optional.ofNullable(parseObject(template.opsForValue().get(key), valueType));
    }


    public Boolean getBit(String key, Integer i) {
        return template.opsForValue().getBit(key, i);
    }

    /**
     * 增加(自增长), 负数则为自减
     *
     * @param key
     * @param delta
     * @return
     */
    public Long inc(final String key, long delta) {
        return template.opsForValue().increment(key, delta);
    }

    /**
     * 增加(自增长), 负数则为自减
     *
     * @param key
     * @param delta
     * @return
     */
    public Double inc(final String key, double delta) {
        return template.opsForValue().increment(key, delta);
    }

    /**
     * 追加到末尾
     *
     * @param key
     * @param value
     * @return
     */
    public Integer append(String key, String value) {
        return template.opsForValue().append(key, value);
    }


    /**
     * 获取字符串的长度
     *
     * @param key
     * @return
     */
    public Long size(String key) {
        return template.opsForValue().size(key);
    }

    /**
     * 用 value 参数覆写给定 key 所储存的字符串值，从偏移量 offset 开始
     *
     * @param key
     * @param value
     * @param offset 从指定位置开始覆写
     */
    public void setRange(String key, String value, long offset) {
        template.opsForValue().set(key, value, offset);
    }

    /**
     * 批量添加
     *
     * @param maps
     */
    public void multiSet(Map<String, String> maps) {
        template.opsForValue().multiSet(maps);
    }

    /*------------------List Operation------------------*/

    public <T> Long rightPush(String key, T obj) {
        String json = JSONObject.toJSONString(obj);
        return template.opsForList().rightPush(key, json);
    }

    public <T> Optional<T> leftPop(String key, Class<T> clazz) {
        String val = template.opsForList().leftPop(key);
        if (!Objects.isNull(val)) {
            return Optional.ofNullable(parseObject(val, clazz));
        }
        return Optional.empty();
    }

    public <T> Long leftPush(String key, T obj) {
        String json = JSONObject.toJSONString(obj);
        return template.opsForList().leftPush(key, json);
    }

    public <T> T rightPop(String key, Class<T> clazz) {
        return parseObject(template.opsForList().rightPop(key), clazz);
    }

    public <T> Long rightPushAll(String key, List<T> list) {
        List<String> collect = list.stream().map(JSONObject::toJSONString).collect(Collectors.toList());
        return template.opsForList().rightPushAll(key, collect);
    }

    public <T> List<T> lRange(String key, long start, long stop, Class<T> clazz) {
        List<String> results = template.opsForList().range(key, start, stop);
        if (CollectionUtils.isEmpty(results)) {
            return Collections.emptyList();
        }
        return results.stream().filter(Objects::nonNull).map(r -> JSONObject.parseObject(r, clazz)).collect(Collectors.toList());
    }

    public void lTrim(String key, long start, long stop) {
        template.opsForList().trim(key, start, stop);
    }

    /*------------------Set Operation------------------*/

    public void sAdd(String key, String... value) {
        template.opsForSet().add(key, value);
    }

    public boolean sIsMember(String key, Object value) {
        Boolean b = template.opsForSet().isMember(key, value);
        return b != null && b;
    }

    public Set<String> sMembers(String key) {
        return template.opsForSet().members(key);
    }

    public Set<String> sDistinctRandomMembers(String key, long count) {
        return template.opsForSet().distinctRandomMembers(key, count);
    }

    public Long sRem(String key, String... value) {
        return template.opsForSet().remove(key, (Object[]) value);
    }

    /*------------------ZSet Operation------------------*/

    public Long zCount(String key, double min, double max) {
        return template.opsForZSet().count(key, min, max);
    }

    public Boolean zAdd(String key, String value, double score) {
        return template.opsForZSet().add(key, value, score);
    }

    public Long zRemove(String key, String value) {
        return template.opsForZSet().remove(key, value);
    }

    public Double zIncrementScore(String key, String value, double score) {
        return template.opsForZSet().incrementScore(key, value, score);
    }

    public Double zGetScore(String key, String value) {
        return template.opsForZSet().score(key, value);
    }

    public List<ZSetOperations.TypedTuple<String>> zRangeWithScores(String key, Long min, Long max) {
        Set<ZSetOperations.TypedTuple<String>> typedTuples = template.opsForZSet().rangeWithScores(key, min, max);
        return new ArrayList<>(Objects.requireNonNull(typedTuples));
    }

    /**
     * 原文https://gitee.com/2016Young/codes/mvrlu9wtied6jpc10287z67
     * 加锁
     *
     * @param key   被秒杀商品的id
     * @param value 当前线程操作时的 System.currentTimeMillis() + 2000，2000是超时时间，这个地方不需要去设置redis的expire，
     *              也不需要超时后手动去删除该key，因为会存在并发的线程都会去删除，造成上一个锁失效，结果都获得锁去执行，并发操作失败了就。
     * @return
     */
    public Boolean lock(String key, String value) {
        //如果key值不存在，则返回 true，且设置 value
        Boolean aBoolean = template.opsForValue().setIfAbsent(key, value);
        if (aBoolean) {
            return true;
        }

        //获取key的值，判断是是否超时
        String curVal = template.opsForValue().get(key);
        if (curVal != null && Long.parseLong(curVal) < System.currentTimeMillis()) {
            //获得之前的key值，同时设置当前的传入的value。这个地方可能几个线程同时过来，但是redis本身天然是单线程的，所以getAndSet方法还是会安全执行，
            //首先执行的线程，此时curVal当然和oldVal值相等，因为就是同一个值，之后该线程set了自己的value，后面的线程就取不到锁了
            String oldVal = template.opsForValue().getAndSet(key, value);
            return oldVal != null && oldVal.equals(curVal);
        }
        return false;
    }

    /**
     * 解锁
     *
     * @param key
     * @param value
     */
    public void unlock(String key, String value) {
        try {
            String curVal = template.opsForValue().get(key);
            if (curVal != null && curVal.equals(value)) {
                template.opsForValue().getOperations().delete(key);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /*------------------Hash Operation------------------*/

    public <HK, HV> void hPut(String key, HK hashKey, HV value) {
        String json = JSONObject.toJSONString(value);
        template.opsForHash().put(key, hashKey, json);
    }

    public <HK, HV> void hPutAll(String key, Map<? extends HK, ? extends HV> m) {
        template.opsForHash().putAll(key, m);
    }

    public <HK> long hInc(String key, HK hashKey, long delta) {
        return template.opsForHash().increment(key, hashKey, delta);
    }

    public long hDelete(String key, Object... hashKeys) {
        return template.opsForHash().delete(key, Arrays.stream(hashKeys).toArray());
    }

    public <HK, HV> Map<HK, HV> hMap(String key) {
        return template.<HK, HV>opsForHash().entries(key);
    }

    public <HK, HV> Set<HK> hKeys(String key) {
        return template.<HK, HV>opsForHash().keys(key);
    }

    public <HK, HV> List<HV> hValues(String key) {
        return template.<HK, HV>opsForHash().values(key);
    }

    public <HK, R> R hGet(String key, HK hashKey, Class<R> returnType) {
        String json = template.<String, String>opsForHash().get(key, hashKey);
        return JSONObject.parseObject(json, returnType);
    }

    public <HK, R> Optional<String> hGet(String key, HK hashKey) {
        return Optional.ofNullable(template.<String, String>opsForHash().get(key, hashKey));
    }

    public <HK> boolean hExists(String key, HK hashKey) {
        return template.opsForHash().hasKey(key, hashKey);
    }

    public long hLen(String key) {
        return template.opsForHash().size(key);
    }

    public <HK, HV> boolean hSetIfAbsent(String key, HK hashKey, HV value) {
        return template.opsForHash().putIfAbsent(key, hashKey, value);
    }

    @Nullable
    private <T> T parseObject(String text, Class<T> clazz) {
        if (StringUtils.isBlank(text)) {
            return null;
        } else {
            return JSONObject.parseObject(text, clazz);
        }
    }

    /**
     * 尝试获取全局锁
     *
     * @param lock 锁的名称
     * @return true 获取成功，false获取失败
     */
    public boolean tryLock(Lock lock) {
        return getLock(lock.getName(), lock.getValue(), LOCK_TRY_TIMEOUT, LOCK_TRY_INTERVAL, LOCK_EXPIRE);
    }


    /**
     * 操作redis获取全局锁
     *
     * @param key            锁的名称
     * @param timeout        获取的超时时间
     * @param tryInterval    多少ms尝试一次
     * @param lockExpireTime 获取成功后锁的过期时间
     * @return true 获取成功，false获取失败
     */
    public boolean getLock(String key, String value, long timeout, long tryInterval, long lockExpireTime) {
        try {
            if (StringUtils.isEmpty(key)) {
                return false;
            }
            long startTime = System.currentTimeMillis();
            while (true) {
                if (Boolean.FALSE.equals(template.hasKey(key))) {
                    ValueOperations<String, String> ops = template.opsForValue();
                    if (Boolean.TRUE.equals(ops.setIfAbsent(key, value, lockExpireTime, TimeUnit.MILLISECONDS))) {
                        return true;
                    }
                } else {//存在锁
                    log.debug("lock is exist!！！");
                }
                //尝试超过了设定值之后直接跳出循环
                if (System.currentTimeMillis() - startTime > timeout) {
                    return false;
                }
                Thread.sleep(tryInterval);
            }
        } catch (InterruptedException e) {
            log.error(e.getMessage());
            return false;
        }
    }

    /**
     * 释放锁
     */
    public void releaseLock(String key) {
        if (!StringUtils.isEmpty(key)) {
            template.delete(key);
        }
    }


}